package com.chenu.gamscriptman.controller;

import com.chenu.gamscriptman.compoent.Global;
import com.chenu.gamscriptman.domain.Device;
import com.chenu.gamscriptman.domain.GameData;
import com.chenu.gamscriptman.domain.User;
import com.chenu.gamscriptman.domain.UserData;
import com.chenu.gamscriptman.domain.noindb.Error;
import com.chenu.gamscriptman.domain.noindb.Message;
import com.chenu.gamscriptman.service.DocumentTask;
import com.chenu.gamscriptman.service.MessageService;
import com.chenu.gamscriptman.service.UserService;
import com.chenu.gamscriptman.service.impl.*;
import com.chenu.gamscriptman.utils.GoUtils;
import com.google.gson.Gson;
import io.swagger.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
 * @author flyan
 * date 2019-12-24
 * @function 消息处理者/消息生产者
 * 完成消息通信的处理，同时包含消息数据库的相关操作，同时，其也是消息的生产者
 */
@Api(value = "消息处理者", tags = {"系统操作接口"})
@Controller
public class MessageController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /* GSON，用于json解析 */
    @Autowired
    private Gson gson;

    /* 消息服务 */
    @Autowired
    private MessageService messageService;

    /* 用户服务 */
    @Autowired
    private UserService userService;

    /* redis模板，用于缓存和处理结果的暂存 */
    @Autowired
    private RedisTemplate jsonRedisTemplate;

    /* 发送并接收一条不携带任何数据的消息，用作测试 */
    @GetMapping("/send/{source}/{type}/{needView}/{dest}")
    @ResponseBody
    @ApiIgnore
    public Message sendRecNoDada(
            @PathVariable("source") Long source,
            @PathVariable("type") int type,
            @PathVariable("needView") boolean needView,
            @PathVariable("dest") int dest
    ){
        logger.info("send and receive message no any data");
        /* 封装消息 */
        Message message = new Message();
        message.setSource(source);
        message.setType(type);
        message.setNeedView(needView);
        /* 发送并接收消息 */
        return messageService.sendAndReceive(dest, message);
    }

    /**
     * 发送一条携带数据的消息，当然，不给也可以不带
     * 返回一条处理结果消息，以json的格式
     */
    @ApiOperation(value = "发送一条消息，得到html数据回应", notes = "发送一条携带数据的消息给服务器的某个任务，" +
            "任务解析消息并处理，然后回发一条包含处理结果的消息json数据，这个调用是提供给系统开发者的，如果只是为了使用系统，请忽略")
    @PostMapping("/send/nv")
    @ResponseBody
    public Message sendRecNoView(
            @ApiParam(name = "消息", value = "请求发送携带的消息", required = true)
                    Message message,        /* 消息 */
            @ApiParam(name = "文件", value = "这次发送消息是否需要上传文件，如果需要，那么这里将封装文件。",required = false)
                    MultipartFile file,     /* 上传的文件 */
            @ApiParam(name = "发送目标", value = "该条消息需要发送给哪个任务？", required = true)
                    int dest                /* 发给谁呢？ */
    ) {
        logger.info("请求不需要视图的消息服务... ");
        logger.info("请求消息 --> " + message.toString() + " --> " + dest);
        Message rs = null;

        /* 先判断用户源正不正确 */
        if(message.getSource() != 0 && userService.isExists(message.getSource()) == null) {
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.MESSAGE_TASK,
                    Message.HANDLER_FAILURE,
                    "{error: " + Message.ERROR_BAD_USER_SOURCE +
                            ", tip: '错误的用户源，发送消息的用户不存在...'}"
            );
        }

        /* 请求错误？ */
        if (message.isNeedView()) {
            return MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.MESSAGE_TASK,
                    Message.HANDLER_FAILURE,
                    "{error: " + Message.ERROR_REQUEST_TYPE +
                            ", tip: '错误的请求方式，需要视图请通过'/send/wv'的方式请求...'}"
            );
        }

        /* 设置上传的文件数据 */
        Global.UPLOAD_FILE = file;
        if (file != null) logger.info("上传的文件名称 --> " + file.getOriginalFilename());

        /* 发送消息给服务器请求服务，并得到响应的消息 */
        rs = messageService.sendAndReceive(dest, message);

        /* 服务器未响应？ */
        if(rs == null) {        /* 消息发送过去了，但服务器未给出回应 */
            rs = MessageServiceImpl.TaskFinish(
                    message,
                    (long) MessageServiceImpl.MESSAGE_TASK,
                    Message.HANDLER_FAILURE,
                    "{error: " + Message.ERROR_NO_RESPOND +
                            ", tip: '服务未响应...'}"
            );
        }

        /* 返回包含处理结果的消息 */
        return rs;
    }

    /**
     * 发送一条携带数据的消息，当然，不给也可以不带
     * 返回一个网页视图
     */
    @ApiOperation(value = "发送一条消息，得到json数据回应", notes = "发送一条携带数据的消息给服务器的某个任务，" +
            "任务解析消息并处理，然后回发一个html页面，这个调用是提供给系统开发者的，如果只是为了使用系统，请忽略")
    @PostMapping("/send/wv")
    public String sendWithView(
            @ApiParam(name = "消息", value = "请求发送携带的消息", required = true)
                    Message message,        /* 消息 */
            @ApiParam(name = "文件", value = "这次发送消息是否需要上传文件，如果需要，那么这里将封装文件。",required = false)
                    MultipartFile file,     /* 上传的文件 */
            @ApiParam(name = "发送目标", value = "该条消息需要发送给哪个任务？", required = true)
                    int dest,               /* 发给谁呢？ */
            Model model
    ){
        logger.info("请求需要视图的消息服务... ");
        logger.info("请求消息 --> " + message.toString() + " --> " + dest);
        Message rs = null;
        Error error = new Error();

        /* 先判断用户源正不正确 */
        if(message.getSource() != 0 && userService.isExists(message.getSource()) == null) {
            error.setErrorNo(Message.ERROR_BAD_USER_SOURCE);
            error.setTip("错误的用户源，发送消息的用户不存在...");
            model.addAttribute("err_msg", error);
            return "error";
        }

        /* 请求错误？ */
        if (!message.isNeedView()) {
            error.setErrorNo(Message.ERROR_REQUEST_TYPE);
            error.setTip("错误的请求方式，不需要视图请通过'/send/nv'的方式请求...");
            model.addAttribute("err_msg", error);
            /* 返回错误页面 */
            return "error";
        }

        /* 设置上传的文件数据 */
        Global.UPLOAD_FILE = file;
        if (file != null) logger.info("上传的文件名称 --> " + file.getOriginalFilename());

        /* 发送消息给服务器请求服务，并得到响应的消息 */
        rs = messageService.sendAndReceive(dest, message);

        /* 服务器未响应？ */
        if(rs == null) {        /* 消息发送过去了，但服务器未给出回应 */
            error.setErrorNo(Message.ERROR_NO_RESPOND);
            error.setTip("服务未响应...");
            model.addAttribute("err_msg", error);
            return "error";
        }

        /* 处理失败 */
        if(rs.getType() == Message.HANDLER_FAILURE){
            error.setErrorNo(Message.ERROR_NORMAL);
            error.setTip(rs.getData());
            model.addAttribute("err_msg", error);
            return "error";
        }

        /* 将消息放入模型中 */
        model.addAttribute("message", rs);
        /* 成功返回处理结果指定的试图 */
        return GoUtils.RedirectUrl(rs.getData());
    }

    /**
     * 下载一个文件从服务器
     * 下载的文件真实路径已经存在Redis缓存服务器中，所以需要在调用这个接口前完成文件的
     * 创建、设置、获取等一些列工作。
     */
    @GetMapping("/download")
    @ResponseBody
    @ApiOperation(value = "下载一个文件", notes = "处理一个已经创建文件完成并将文件路径设置到缓存中的一条下载请求，" +
            "接口得到这个文件并传输给用户，这个调用是提供给系统开发者的，如果只是为了使用系统，请忽略，它并不能被直接调用")
    public String download(
            HttpServletRequest request,
            HttpServletResponse response
    ) throws UnsupportedEncodingException {

        /* 得到要下载的文件路径 */
        String filePath = (String) jsonRedisTemplate.opsForValue().get(Global.REDIS_DOWNLOAD_FILE_PATH);
        String[] temp = filePath.split("/");
        String fileName = temp[temp.length - 1];
        logger.debug("下载文件 --> " + fileName);

        /* 这个文件存在吗？ */
        File file = new File(filePath);
        if(file.exists()){
            /* 准备开始下载文件 */
            response.setContentType("application/force-download");  /* 设置强制下载不打开 */
            response.addHeader("Content-Disposition",
                    "attachment;fileName=" + URLEncoder.encode(fileName, "utf-8"));   /* 设置下载的文件名，记得转码 */
            byte[] buffer = new byte[1024];
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            try{
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                OutputStream os = response.getOutputStream();
                int i = bis.read(buffer);
                while(i != -1){
                    os.write(buffer,0, i);
                    i = bis.read(buffer);
                }
                logger.debug("--> " + fileName + "文件传输完毕！");
                return "文件下载成功";
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (bis != null){
                    try{
                        bis.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                if (fis != null){
                    try{
                        fis.close();
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }

        return "文件下载失败";
    }

    /**
     * 前往API页面
     */
    @GetMapping("/api")
    @ApiOperation(value = "查看系统API", notes = "跳转到系统API文档页面，就是当前页面。")
    public String api(){
        return GoUtils.RedirectUrl("/swagger-ui.html");
    }

    /* ======================== 辰湖游戏管理系统所有公开的接口调用（API）======================== */
    /* ============== 设备信息相关 ============== */
    /* 添加或更新一条设备信息 */
    @PostMapping("/device/save_or_update")
    @ResponseBody
    @ApiOperation(value = "保存或更新一条设备的信息", notes = "请求头中给出用户验证信息（用户名和密码），请求数据给出一个" +
            "设备信息，如果该条设备不存在且信息正确，这条信息将会被保存；如果该条设备先前已经存在，这条信息将会被更新覆盖到旧的" +
            "设备信息。  \n" +
            "提示：设备信息中如果某个属性并不需要更新，置为null即可。")
    public Message deviceSaveOrUpdate(
            HttpServletRequest request,
            @ApiParam(name = "设备信息", value = "json格式的设备信息", required = true)
            @RequestBody
                    Device device
    ) {
        logger.debug("/device/save_or_update");
        Message message = new Message();
        Long source;
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message)) return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(DeviceTaskImpl.DEVICE_ADD_OR_UPDATE);   /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        message.setData(gson.toJson(device));                   /* 将数据放入 */
        return sendRecNoView(message, null, MessageServiceImpl.DEVICE_TASK);    /* 发起调用 */
    }

    /* 验证一个用户是否合法，如果合法设置其消息用户源为这个用户 */
    private boolean verifyAccount(String username, String password, Message message){
        Long source;

        logger.debug("--> " + username + ", " + password);
        /* 用户可能未指定用户名和密码 */
        if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
            MessageServiceImpl.TaskFinish(       /* 也不是固有用户 */
                    message,
                    (long) MessageServiceImpl.MESSAGE_TASK,
                    Message.HANDLER_FAILURE,
                    "{error: " + Message.ERROR_BAD_USER_SOURCE +
                            ", tip: '错误的用户，未指定用户名或密码！'}"
            );
            return false;
        }

        /* 验证 */
        User user = userService.login(username, password);
        logger.debug("--> " + user);
        if(user == null) {      /* 不在数据库中 */

            if("chenu".equals(username) && "flyan@chenu".equals(password)){
                source = 0L;    /* 系统固有用户 */
            } else {
                MessageServiceImpl.TaskFinish(       /* 也不是固有用户 */
                        message,
                        (long) MessageServiceImpl.MESSAGE_TASK,
                        Message.HANDLER_FAILURE,
                        "{error: " + Message.ERROR_BAD_USER_SOURCE +
                                ", tip: '错误的用户，请检查你的用户名和密码！'}"
                );
                return false;
            }
        } else {
            source = user.getUid();     /* 数据库中的用户 */
        }

        message.setSource(source);                              /* 设置用户源 */
        return true;
    }

    /* 获取一条设备信息 */
    @PostMapping("/device/get")
    @ResponseBody
    @ApiOperation(value = "获取设备信息", notes = "请求头中请给出用户验证信息（用户名和密码），请求数据中给出：设备id/MAC地址/设备号" +
            "中的任意一个，如果给出多个条件，则优先级顺序：设备id > MAC地址 > 设备号，接口将返回该设备的设备信息。  \n" +
            "设备信息将会以json字符串被包含在返回的data中，一个设备对象所有字段信息如下：  \n" +
            "设备对象 {  \n" +
            "adminUsername (字符串类型): 设备的管理员（用户名），不必手动填写，由系统自动检测生成 ,  \n" +
            "battery (字符串类型): 电池状态信息 ,  \n" +
            "extenInt (数值类型): 拓展数值型，存放用户一些自己的数字值。 ,  \n" +
            "extenString (字符串类型): 拓展字符串，存放用户一些自己的字符串值。 ,  \n" +
            "extranetIp (字符串类型): 外网IP ,  \n" +
            "id (数值类型): id ,  \n" +
            "imei (字符串类型): 设备IMEI标识码 ,  \n" +
            "intranetIp (字符串类型): 内网IP ,  \n" +
            "mac (字符串类型): 物理地址 ,  \n" +
            "memory (字符串类型): 内存空间 ,  \n" +
            "model (字符串类型): 设备型号 ,  \n" +
            "name (字符串类型): 设备名称 ,  \n" +
            "netType (字符串类型): 网络类型 ,  \n" +
            "nrSameIp (数值类型): 相同内网IP的设备数量，不必手动填写，系统自动检测生成。 ,  \n" +
            "runGame (字符串类型): 设备运行的游戏名，不必手动填写，由系统自动检测生成 ,  \n" +
            "sdevNr (字符串类型): 设备号，可以添加分组，分组号支持26个字母，格式：分组号 + 次设备号，分组号不给等于在默认分组，即不分组 ,  \n" +
            "serialNr (字符串类型): 设备序列号 ,  \n" +
            "storage (字符串类型): 存储空间 ,  \n" +
            "sysVersion (字符串类型): 搭载系统版本 ,  \n" +
            "uptime (数值类型): 设备更新时间（时间戳形式），不必手动填写，由系统自动检测生成 ,  \n" +
            "uuid (字符串类型): 给设备分配的uuid，需要注意的是，如果生成的uuid带-字符，请先将它们去除  \n" +
            "}  \n" +
            "您可以根据自己的需要根据字段名称获取里面的字段的各个值。")
    public Message deviceGet(
            HttpServletRequest request,
            @ApiParam(name = "设备信息", value = "json格式的设备信息", required = true)
            @RequestBody
                    Device device
    ) {
        Message message = new Message();
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message)) return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(DeviceTaskImpl.DEVICE_GET);   /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        message.setData(gson.toJson(device));                   /* 将数据放入 */
        return sendRecNoView(message, null, MessageServiceImpl.DEVICE_TASK);    /* 发起调用 */
    }

    /* ============== 用户数据相关 ============== */
    /* 获取一条用户数据 */
    @PostMapping("/userdata/get")
    @ResponseBody
    @ApiOperation(value = "获取一个设备的用户数据", notes = "请求头中请给出用户验证信息（用户名和密码），请求数据中给出：" +
            "[设备号 + 大区 + 服务器 + 角色编号 + 运行游戏]，它们唯一标识一条用户数据，然后接口将返回该条用户数据的json数据。  \n" +
            "用户数据将会以json字符串被包含在返回的data中，一个用户数据对象所有字段信息如下：  \n" +
            "用户数据 {  \n" +
            "adminUsername (字符串类型): 用户数据的管理员（用户名），不必手动填写，由系统自动检测生成 ,  \n" +
            "data (字符串类型): 数据信息，由用户自定义的映射决定，以json字符串格式存放，需要先解析再使用 ,  \n" +
            "gameName (字符串类型): 指出用户数据来源于什么游戏 ,  \n" +
            "gameTask (游戏任务，这也是一个对象，具体请看下面的游戏任务体),  \n" +
            "id (数值类型): id ,  \n" +
            "uptime (数值类型): 用户数据更新时间（时间戳形式），不必手动填写，由系统自动检测生成  \n" +
            "}  \n" +
            "游戏任务 {  \n" +
            "region (数值类型): 游戏角色所在大区 ,  \n" +
            "role (数值类型): 游戏角色编号 ,  \n" +
            "sdevNr (字符串类型): 设备号，可以添加分组，分组号支持26个字母，格式：分组号 + 次设备号，分组号不给等于在默认分组，即不分组 ,  \n" +
            "server (字符串类型): 游戏角色所在服务器  \n" +
            "}  \n" +
            "您可以根据自己的需要根据字段名称获取里面的字段的各个值。")
    public Message userdataGet(
            HttpServletRequest request,
            @ApiParam(name = "用户数据", value = "json格式的用户数据", required = true)
            @RequestBody
                    UserData userData
    ){
        Message message = new Message();
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message))return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(UserDataTaskImpl.USERDATA_GET);         /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        message.setData(gson.toJson(userData));                 /* 将数据放入 */
        return sendRecNoView(message, null, MessageServiceImpl.USERDATA_TASK);    /* 发起调用 */
    }

    /* 添加或更新一条用户数据 */
    @PostMapping("/userdata/save_or_update")
    @ResponseBody
    @ApiOperation(value = "保存或更新一个设备的用户数据", notes = "请求头中请给出用户验证信息（用户名和密码），请求数据中给出：" +
            "用户数据，如果该条用户数据不存在且数据正确，这条用户数据将会被保存；如果该条用户数据先前已经存在，这条将会被更新覆盖到旧的" +
            "用户数据。")
    public Message userdataSaveOrUpdate(
            HttpServletRequest request,
            @ApiParam(name = "用户数据", value = "json格式的用户数据", required = true)
            @RequestBody
                    UserData userData
    ) {
        logger.debug("/userdata/save_or_update");
        Message message = new Message();
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message)) return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(UserDataTaskImpl.USERDATA_ADD_OR_UPDATE);   /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        message.setData(gson.toJson(userData));                   /* 将数据放入 */
        return sendRecNoView(message, null, MessageServiceImpl.USERDATA_TASK);    /* 发起调用 */
    }

    /* ============== 游戏数据相关 ============== */
    /* 获取一个设备的游戏数据 */
    @PostMapping("/gamedata/get")
    @ResponseBody
    @ApiOperation(value = "获取一个设备的游戏数据", notes = "请求头中请给出用户验证信息（用户名和密码），" +
            "请求数据中给出：设备id/MAC地址/设备号中的任意一个，如果给出多个条件，" +
            "则优先级顺序：设备id > MAC地址 > 设备号，接口将返回该设备的游戏数据，它们用于调度设备。  \n" +
            "游戏数据将会以json字符串被包含在返回的data中，一个游戏数据对象所有字段信息如下：  \n" +
            "游戏数据 {  \n" +
            "id (数值类型): id，不必管它数据库需要而已 ,  \n" +
            "devRange (字符串类型): 设备范围，表示该游戏数据被哪些设备使用 ,  \n" +
            "regionServerRange (字符串类型): 区服范围，表示设备应该在什么大区什么服务器创建角色 ,  \n" +
            "nrRole (数值类型):  角色数量，即该每台设备应该有几个角色。,  \n" +
            "gameName (字符串类型): 要求设备运行的游戏名 ,  \n" +
            "data (字符串类型): 下发任务数据就在这里面，由用户自定义的映射决定，以json字符串存放，需要先解析再使用 ,  \n" +
            "}  \n" +
            "您可以根据自己的需要根据字段名称获取里面的字段的各个值。")
    public Message gamedataGet(
            HttpServletRequest request,
            @ApiParam(name = "设备信息", value = "json格式的设备信息", required = true)
            @RequestBody Device device
    ){
        Message message = new Message();
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message))return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(GameDataTaskImpl.GAMEDATA_GET);         /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        message.setData(gson.toJson(device));                   /* 将数据放入 */
        return sendRecNoView(message, null, MessageServiceImpl.GAMEDATA_TASK);    /* 发起调用 */
    }

    /* ============== 文档相关 ============== */
    /* 获取文档内容 */
    @PostMapping("/doc/get")
    @ResponseBody
    @ApiOperation(value = "获取文档内容", notes = "请求头中请给出用户验证信息（用户名和密码）")
    public Message documentGet(
            HttpServletRequest request
    ) {
        Message message = new Message();
        /* 验证用户 */
        if(!verifyAccount(request.getHeader("username"), request.getHeader("password"), message))return message;

        /* 用户验证通过，可以发送消息申请一条调用了 */
        message.setType(DocumentTaskImpl.DOCUMENT_GET);         /* 想要什么服务？ */
        message.setNeedView(false);                             /* 不需要视图 */
        return sendRecNoView(message, null, MessageServiceImpl.DOCUMENT_TASK);    /* 发起调用 */
    }

}
